package com.fitso.model.service.food;

import org.apache.commons.lang.ClassUtils;
import org.springframework.stereotype.Component;

import com.fitso.model.bean.measure.Measure;
import com.fitso.model.bean.measure.energy.Calorie;
import com.fitso.model.bean.measure.mass.Gram;
import com.fitso.model.bean.measure.mass.Mass;
import com.fitso.model.bean.measure.volume.MilliLiter;
import com.fitso.model.bean.measure.volume.Volume;
import com.fitso.model.bean.nutrition.Carbohydrates;
import com.fitso.model.bean.nutrition.Cholesterol;
import com.fitso.model.bean.nutrition.Fats;
import com.fitso.model.bean.nutrition.Food;
import com.fitso.model.bean.nutrition.Minerals;
import com.fitso.model.bean.nutrition.Nutrient;
import com.fitso.model.bean.nutrition.Protein;
import com.fitso.model.bean.nutrition.Vitamins;

/**
 * Compacts food of a given serving size to parts per gram/milliliter. Expands
 * parts per gram/milliliter (assuming previous compact) into food of the given
 * serving size.
 * 
 * @author timothystorm
 */
@Component("FoodConversionEngine")
public class FoodConversionEngine implements FoodCompactor, FoodExpander {
	/**
	 * compacts food by dividing the food by the base unit (gram/milliliter)
	 */
	public Food compact(Food food, Measure measure) {
		return new FoodIterator().iterate(food, measure, new PerUnitConversion() {

			public Measure convert(Measure measure, Measure perUnit) {
				return measure == null ? null : measure.clone().divide(perUnit);
			}
		});
	}

	/**
	 * expands food by multiplying the food by the base unit (gram/milliliter)
	 */
	public Food expand(Food food, Measure measure) {
		return new FoodIterator().iterate(food, measure, new PerUnitConversion() {

			public Measure convert(Measure measure, Measure perUnit) {
				return measure == null ? null : measure.clone().multiply(perUnit);
			}
		});
	}

	/**
	 * Interface that acts like a closure for converting {@link Measure}s and
	 * {@link Nutrient}s
	 */
	private interface PerUnitConversion {
		public Measure convert(Measure measure, Measure perUnit);
	}

	/**
	 * Internal class that iterates the food and its constituent parts. Relies
	 * on {@link PerUnitConversion} to determine what should be done with each
	 * food part. It guarantees that no null {@link Measure} and
	 * {@link Nutrient} are passed to the {@link PerUnitConversion}
	 */
	private static class FoodIterator {
		public Food iterate(Food food, Measure measure, PerUnitConversion converter) {
			Food convertedFood = new Food();
			Measure perUnit = null;

			// converts the unit to the base unit of gram/milliliter
			if (ClassUtils.isAssignable(measure.getClass(), Mass.class)) {
				perUnit = new Gram((Mass) measure);
			} else if (ClassUtils.isAssignable(measure.getClass(), Volume.class)) {
				perUnit = new MilliLiter((Volume) measure);
			} else {
				throw new IllegalArgumentException("can't handle units: " + measure.getClass());
			}

			Calorie cal = food.getCalories();
			if (cal != null) {
				Calorie conCal = new Calorie(converter.convert(cal, perUnit).getValue());
				convertedFood.setCalories(conCal);
			}

			Carbohydrates carbs = food.getCarbs();
			if (carbs != null) {
				Carbohydrates conCarbs = new Carbohydrates((Mass) converter.convert(carbs.getMass(), perUnit));
				conCarbs.setFiber((Mass) converter.convert(carbs.getFiber(), perUnit));
				conCarbs.setSugar((Mass) converter.convert(carbs.getSugar(), perUnit));
				convertedFood.setCarbs(conCarbs);
			}

			Cholesterol chol = food.getCholesterol();
			if (chol != null && chol.getMass() != null) {
				Cholesterol conChol = new Cholesterol((Mass) converter.convert(chol.getMass(), perUnit));
				convertedFood.setCholesterol(conChol);
			}

			Fats fats = food.getFats();
			if (fats != null) {
				Fats conFats = new Fats((Mass) converter.convert(fats.getMass(), perUnit));
				conFats.setMonoUnsaturated((Mass) converter.convert(fats.getMonoUnsaturated(), perUnit));
				conFats.setPolyUnsaturated((Mass) converter.convert(fats.getPolyUnsaturated(), perUnit));
				conFats.setSaturated((Mass) converter.convert(fats.getSaturated(), perUnit));
				conFats.setTrans((Mass) converter.convert(fats.getTrans(), perUnit));
				convertedFood.setFats(conFats);
			}

			Protein protein = food.getProtein();
			if (protein != null) {
				Protein conProtein = new Protein((Mass) converter.convert(protein.getMass(), perUnit));
				convertedFood.setProtein(conProtein);
			}

			Minerals min = food.getMinerals();
			if (min != null) {
				Minerals conMin = new Minerals();
				conMin.setBoron((Mass) converter.convert(min.getBoron(), perUnit));
				conMin.setCalcium((Mass) converter.convert(min.getCalcium(), perUnit));
				conMin.setChloride((Mass) converter.convert(min.getChloride(), perUnit));
				conMin.setChromium((Mass) converter.convert(min.getChromium(), perUnit));
				conMin.setCopper((Mass) converter.convert(min.getCopper(), perUnit));
				conMin.setIodine((Mass) converter.convert(min.getIodine(), perUnit));
				conMin.setIron((Mass) converter.convert(min.getIron(), perUnit));
				conMin.setMagnesium((Mass) converter.convert(min.getMagnesium(), perUnit));
				conMin.setManganese((Mass) converter.convert(min.getManganese(), perUnit));
				conMin.setMolybdenum((Mass) converter.convert(min.getMolybdenum(), perUnit));
				conMin.setNickel((Mass) converter.convert(min.getNickel(), perUnit));
				conMin.setPhosphorus((Mass) converter.convert(min.getPhosphorus(), perUnit));
				conMin.setPotassium((Mass) converter.convert(min.getPotassium(), perUnit));
				conMin.setSelenium((Mass) converter.convert(min.getSelenium(), perUnit));
				conMin.setSilicon((Mass) converter.convert(min.getSilicon(), perUnit));
				conMin.setSodium((Mass) converter.convert(min.getSodium(), perUnit));
				conMin.setTin((Mass) converter.convert(min.getTin(), perUnit));
				conMin.setVanadium((Mass) converter.convert(min.getVanadium(), perUnit));
				conMin.setZinc((Mass) converter.convert(min.getZinc(), perUnit));
				convertedFood.setMinerals(conMin);
			}

			Vitamins vit = food.getVitamins();
			if (vit != null) {
				Vitamins conVit = new Vitamins();
				conVit.setA((Mass) converter.convert(vit.getA(), perUnit));
				conVit.setB((Mass) converter.convert(vit.getB(), perUnit));
				conVit.setB12((Mass) converter.convert(vit.getB12(), perUnit));
				conVit.setB6((Mass) converter.convert(vit.getB6(), perUnit));
				conVit.setBiotin((Mass) converter.convert(vit.getBiotin(), perUnit));
				conVit.setC((Mass) converter.convert(vit.getC(), perUnit));
				conVit.setD((Mass) converter.convert(vit.getD(), perUnit));
				conVit.setE((Mass) converter.convert(vit.getE(), perUnit));
				conVit.setFolate((Mass) converter.convert(vit.getFolate(), perUnit));
				conVit.setK((Mass) converter.convert(vit.getK(), perUnit));
				conVit.setNiacin((Mass) converter.convert(vit.getNiacin(), perUnit));
				conVit.setPantothenicAcid((Mass) converter.convert(vit.getPantothenicAcid(), perUnit));
				conVit.setRiboflavin((Mass) converter.convert(vit.getRiboflavin(), perUnit));
				conVit.setThiamine((Mass) converter.convert(vit.getThiamine(), perUnit));
				convertedFood.setVitamins(conVit);
			}

			convertedFood.setDescription(food.getDescription());
			convertedFood.setServing(food.getServing());
			return convertedFood;
		}
	}
}
